/**
Copyright 2011 Steven Moxley

This file is part of Joggle.

Joggle is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.

Joggle is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
GNU General Public License for more details.

You should have received a copy of the GNU General Public License
along with Joggle.  If not, see <http://www.gnu.org/licenses/>.
*/
package edu.gwu.cs.joggle;

import java.util.ArrayList;
import java.util.Random;
import java.security.SecureRandom;

import com.almworks.sqlite4java.SQLiteException;

/**
 * @author      Steven Moxley <steven.moxley@gmail.com>
 * @version     1              
 * @since       2011-12-20
 */
public class Game {
	
	// variables needed to create a new game on server
	protected Server server;					// the Server hosting this Game
	protected Integer minPlayers;				// minimum number of Players who need to join game before it can begin
	protected Integer maxPlayers;				// maximum number of Players allowed to join this game
	protected Integer minPoints;				// minimum skill required to join this game
	protected Integer maxPoints;				// maximum skill allowed to join this game
	protected Integer maxWait;					// maximum number of seconds to wait for maxPlayers to join after minPlayers have joined before starting game
	protected Integer duration;					// number of seconds the game will last
	protected Integer seed;						// seed for game board generation algorithm; good for auditing
	protected Integer size;						// length and width of board
	protected Player owner;						// Player who created this game (the owner who's allowed to change its properties before it starts)
	protected ArrayList<String> players;		// Players who have joined this game
	protected ArrayList<String> allowedPlayers;	// a whitelist of Players allowed to join this Game
	protected ArrayList<String> bannedPlayers;	// a blacklist of Players not allowed to join this Game
	protected char[][] board;					// test representation of game board
	protected SecureRandom seeder;				// random number generator for board seeding
	protected Random boardGenerator;			// random number generator for board generation
	protected ArrayList<Board> gameBoards;		// actual GUI boards from Players
	protected ArrayList<String> dice4;			// dice face values for 4x4 board
	protected ArrayList<String> dice5;			// dice face values for 5x5 board
	protected Dictionary dict;					// legal words
	protected Boolean newGame;					// whether this Game was just created or re-created from db entries
	protected Boolean waiting;					// whether this minPlayers have joined this Game and the timer has started counting down from maxWait
	
	// variables generated during game play
	protected Integer numComplaints;			// number of players who have flagged this game as invalid or suspicious
	protected ArrayList<String> wordLists;		// all Player's submitted word lists; in the form of userName#word1#word2#word3#etc.#
	
    /**
     * @return Server
     */
	protected Server getServer() {
		return server;
	}

	/**
	 * @return Integer The minimum number of Players who need to join game before it can begin
	 */
	protected Integer getMinPlayers() {
		//minPlayers = owner.getServer().getGameMinPlayers(this);
		return minPlayers;
	}

	/**
	 * Sets (overwrites) the minimum number of Players who need to join game before it can begin
	 * @param min Integer
	 */
	protected void setMinPlayers(Integer min) {
		minPlayers = min;
	}

	/**
	 * @return Integer The maximum number of Players allowed to join this game
	 */
	protected Integer getMaxPlayers() {
		//maxPlayers = owner.getServer().getGameMaxPlayers(this);
		return maxPlayers;
	}

	/**
	 * Sets (overwrites) the maximum number of Players allowed to join this game
	 * @param max Integer
	 */
	protected void setMaxPlayers(Integer max) {
		maxPlayers = max;
	}

	/**
	 * @return Integer The minimum skill level required to join this game
	 */
	protected Integer getMinPoints() {
		//minPoints = owner.getServer().getGameMinPoints(this);
		return minPoints;
	}

	/**
	 * Sets (overwrites) the minimum skill level required to join this game
	 * @param min Integer
	 */
	protected void setMinPoints(Integer min) {
		minPoints = min;
	}

	/**
	 * @return Integer The maximum skill allowed to join this game
	 */
	protected Integer getMaxPoints() {
		//maxPoints = owner.getServer().getGameMaxPoints(this);
		return maxPoints;
	}

	/**
	 * Sets (overwrites) the maximum skill allowed to join this game
	 * @param max Integer
	 */
	protected void setMaxPoints(Integer max) {
		maxPoints = max;
	}

	/**
	 * 
	 * @return Integer The maximum number of seconds to wait for maxPlayers to join after minPlayers have joined before starting game
	 */
	protected Integer getMaxWait() {
		//maxWait = owner.getServer().getGameMaxWait(this);
		return maxWait;
	}

	/**
	 * Sets (overwrites) the maximum number of seconds to wait for maxPlayers to join after minPlayers have joined before starting game
	 * @param maxWait Integer
	 */
	protected void setMaxWait(Integer maxWait) {
		this.maxWait = maxWait;
	}

	/**
	 * @return Integer The number of seconds the game will last
	 */
	protected Integer getDuration() {
		/*
		if(!newGame) {
			duration = owner.getServer().getGameDuration(this);
		} */
		return duration;
	}

	/**
	 * Sets (overwrites) the number of seconds the game will last
	 * @param d Integer
	 */
	protected void setDuration(Integer d) {
		duration = d;
	}

	/**
	 * @return Integer The seed for game board generation algorithm (good for auditing)
	 */
	protected Integer getSeed() {
		return seed;
	}

	/**
	 * Sets (overwrites) the seed for game board generation algorithm (good for auditing)
	 * @param s Integer
	 */
	protected void setSeed(Integer s) {
		seed = s;
	}

	/**
	 * @return Integer The length and width of the game board
	 */
	protected Integer getSize() {
		//size = owner.getServer().getGameSize(this);
		return size;
	}

	/**
	 * Sets (overwrites) the length and width of the game board
	 * @param s Integer
	 */
	protected void setSize(Integer s) {
		size = s;
	}

	/**
	 * @return Player The Player who created this game.  (The implied owner who's allowed to change its properties before this Game starts)
	 */
	protected Player getOwner() {
		return owner;
	}

	/**
	 * Sets (overwrites) the Player who owns this game
	 * @param o Player
	 */
	protected void setOwner(Player o) {
		owner = o;
	}

	/**
	 * @return ArrayList<String> The Players who have joined this game
	 */
	protected ArrayList<String> getPlayers() {
		players = owner.getServer().getGamePlayers(this);
		return players;
	}

    /**
     * @return String The string representation of the players who have joined this game
     */
	protected String getPlayersAsString() {
		String result = "";
		for(String s : players) {
			result += s + "#";
		}
		return result;
	}
	
    /**
     * Sets the list of players within this game
     * @param p ArrayList<String>
     */
	protected void setPlayers(ArrayList<String> p) {
		players = p;
	}

	
	/**
	 * @return Integer The number of Players who have joined this game
	 */
	protected Integer getNumPlayers() {
		getPlayers();
		return players.size();
	}
	
    /**
     * @return ArrayList<String> The players who are allowed to play this game
     */
	protected ArrayList<String> getAllowedPlayers() {
		String[] playersAsStrings = owner.getServer().getGameAllowedPlayers(this).split("#");
		for(String s : playersAsStrings) {
			if(!allowedPlayers.contains(s)) {
				allowedPlayers.add(s);
			}
		}
		return allowedPlayers;
	}
	
    /**
     * @return String a String representation of the players who are allowed to play this game
     */
	protected String getAllowedPlayersAsString() {
		String result = "";
		if(allowedPlayers.size() == 0) {
			return result;
		} else {
			for(String s : allowedPlayers) {
				result += s +"#";
			}
			return result;
		}		
	}

    /**
     * @return ArrayList<String> The players who are banned from this game
     */
	protected ArrayList<String> getBannedPlayers() {
		String[] playersAsStrings = owner.getServer().getGameBannedPlayers(this).split("#");
		if(playersAsStrings != null && !playersAsStrings.equals("")) {
			for(String s : playersAsStrings) {
				if(!bannedPlayers.contains(s)) {
					bannedPlayers.add(s);
				}
			}
		}
		return bannedPlayers;
	}

    /**
     * @return String a String representation of the players who are banned from this game
     */
	protected String getBannedPlayersAsString() {
		String result = "";
		if(bannedPlayers.size() == 0) {
			return result;
		} else {
			for(String s : bannedPlayers) {
				result += s +"#";
			}
			return result;
		}
	}
	
	/**
	 * @return Board The Game board
	 */
	protected Board getBoard() {
		return new Board(board, size, this, owner.getUserName());
	}
	
    /**
     * @return Dictionary
     */
	protected Dictionary getDictionary() {
		return dict;
	}

	/**
	 * @param p Adds a new Player to this Game if there are any slots available
	 */
	protected void addPlayer(Player p) {
		if(players.contains(p)) {
			System.out.println("You are already in this game.");
		} else if(players.size() < maxPlayers) {
			server.addGamePlayer(p, this);
			System.out.println("Player " + players.get(players.size()-1) + " joined " + owner.getUserName() + "'s game.");
		} else {
			System.out.println("Unable to join game because the maximum number of Players allowed have already joined.");
		}
	}
	
    /**
     * @return Integer the number of complaints
     */
	protected Integer getNumComplaints() {
		return numComplaints;
	}

    /**
     * Sets the number of complaints for this game
     * @param numComplaints Integer
     */
	protected void setNumComplaints(Integer numComplaints) {
		this.numComplaints = numComplaints;
	}
	
    /**
     * @return ArrayList<String> the word lists associated with this game
     */
	protected ArrayList<String> getWordLists() {
		return wordLists;
	}

    /**
     * sets the word lists associated with this game
     * @param wordLists ArrayList<String>
     */
	protected void setWordLists(ArrayList<String> wordLists) {
		this.wordLists = wordLists;
	}

    /**
     * @return boolean
     */
	protected Boolean isWaiting() {
		return waiting;
	}

    /**
     * @param waiting Boolean
     */
	protected void setWaiting(Boolean waiting) {
		this.waiting = waiting;
	}

	// default constructor
	/**
	 * Create a new Game with default settings
	 * @param o  The Player who creates/owns this Game
	 * @throws SQLiteException 
	 */
	public Game(Player o)  {
		server = o.getServer();
		minPlayers = 2;
		maxPlayers = 10;
		minPoints = 0;
		maxPoints = Integer.MAX_VALUE;
		maxWait = 10000;
		duration = 600000;
		size = 4;
		owner = o;
		dict = server.getAvailableDictionaries().get(0);
		players = new ArrayList<String>(maxPlayers);
		allowedPlayers = new ArrayList<String>();
		bannedPlayers = new ArrayList<String>();
		board = new char[size][size];
		seeder = new SecureRandom();
		seed = seeder.nextInt();
		boardGenerator = new Random(seed);
		gameBoards = new ArrayList<Board>();
		dice4 = new ArrayList<String>();
		dice5 = new ArrayList<String>();
		newGame = true;
		waiting = false;
		
		// to be updated at end of game
		numComplaints = 0;
		wordLists = new ArrayList<String>(players.size());
		
		players.add(owner.getUserName());
		server.addActiveGame(this);
	}
	
	// base constructor
	/**
	 * 
	 * @param minPlay The minimum number of Players who need to join game before it can begin
	 * @param maxPlay The maximum number of Players allowed to join this game
	 * @param minPoint The minimum skill level required to join this game
	 * @param maxPoint The maximum skill level allowed to join this game
	 * @param maxW The maximum number of seconds to wait for maxPlayers to join after minPlayers have joined before starting game
	 * @param d The number of seconds the game will last
	 * @param sd The seed for game board generation algorithm; good for auditing
	 * @param sz The length and width of board
	 * @param o The Player who creates/owns this Game
	 */
	public Game(int minPlay, int maxPlay, int minPoint, int maxPoint, int maxW, int du, int sd, int sz, Dictionary di, Player o, Boolean newG) {
		server = o.getServer();
		minPlayers = minPlay;
		maxPlayers = maxPlay;
		minPoints = minPoint;
		maxPoints = maxPoint;
		maxWait = maxW;
		duration = du;
		size = sz;
		players = new ArrayList<String>(minPlayers);
		allowedPlayers = new ArrayList<String>();
		bannedPlayers = new ArrayList<String>();
		dict = di;
		owner = o;
		board = new char[size][size];
		seeder = new SecureRandom();
		seed = seeder.nextInt();
		boardGenerator = new Random(seed);
		gameBoards = new ArrayList<Board>();
		dice4 = new ArrayList<String>();
		dice5 = new ArrayList<String>();
		newGame = newG;
		waiting = false;
		
		// to be updated at end of game
		numComplaints = 0;
		wordLists = new ArrayList<String>(players.size());
		
		if(newGame) {
			// let Server known a game was created
			players.add(owner.getUserName());
			server.addActiveGame(this);
		}
	}
	
	// constructor with player lists
	/**
	 * 
	 * @param minPlay The minimum number of Players who need to join game before it can begin
	 * @param maxPlay The maximum number of Players allowed to join this game
	 * @param minPoint The minimum skill level required to join this game
	 * @param maxPoint The maximum skill level allowed to join this game
	 * @param maxW The maximum number of seconds to wait for maxPlayers to join after minPlayers have joined before starting game
	 * @param d The number of seconds the game will last
	 * @param sd The seed for game board generation algorithm; good for auditing
	 * @param sz The length and width of board
	 * @param o The Player who creates/owns this Game
	 */
	public Game(Integer minPlay, Integer maxPlay, Integer minPoint, Integer maxPoint, Integer maxW, Integer du, Integer sd, Integer sz, ArrayList<String> p, ArrayList<String> allowed, ArrayList<String> banned, Dictionary di, Player o, Boolean newG) {
		server = o.getServer();
		minPlayers = minPlay;
		maxPlayers = maxPlay;
		minPoints = minPoint;
		maxPoints = maxPoint;
		maxWait = maxW;
		duration = du;
		size = sz;
		allowedPlayers = allowed;
		bannedPlayers = banned;
		dict = di;
		owner = o;
		players = p;
		board = new char[size][size];
		seeder = new SecureRandom();
		seed = seeder.nextInt();
		boardGenerator = new Random(seed);
		gameBoards = new ArrayList<Board>();
		dice4 = new ArrayList<String>();
		dice5 = new ArrayList<String>();
		newGame = newG;
		waiting = false;
		
		// to be updated at end of game
		numComplaints = 0;
		wordLists = new ArrayList<String>(players.size());
		
		if(newGame) {
			// let Server known a game was created
			players.add(owner.getUserName());
			server.addActiveGame(this);
		}
	}
	
	/**
	 * Initialize the Game board by taking a given seed or finding a seed that the Players have not played before
	 */
	protected void initBoard() {
		
		// if no game seed was given in the constructor
		if(seed <= 0) {
			ArrayList<Integer> usedSeeds = new ArrayList<Integer>();
			for(String p : players) {
				String[] playerSeeds = server.getGamesPlayed(p).split("#");
				for(String s : playerSeeds) {
					if(!s.equals("")) {
						usedSeeds.add(new Integer(s));  // no point in caring about duplicates if lists are small
					}
				}
			}
			
			// pick a random unused seed
			seed = Math.abs(boardGenerator.nextInt());
			// make sure none of the Players have used it before
			while(usedSeeds.contains(seed)) {
				seed = Math.abs(boardGenerator.nextInt());
			}
			System.out.println("Game board seeded with " + seed + " initially.");
		}
		
		// Chris' implementation of Boggle-style board generation
		if(size == 4) {
			// dice face values for 4x4 board
			dice4.add("AAEEGN");
			dice4.add("ELRTTY");
			dice4.add("AOOTTW");
			dice4.add("ABBJOO");
			dice4.add("EHRTVW");
			dice4.add("CIMOTU");
			dice4.add("DISTTY");
			dice4.add("EIOSST");
			dice4.add("DELRVY");
			dice4.add("ACHOPS");
			dice4.add("HIMNOU");
			dice4.add("EEINSU");
			dice4.add("EEGHNW");
			dice4.add("AFFKPS");
			dice4.add("HLNNRZ");
			dice4.add("DEILRX");
			
			for(int i = 0; i < size; i++) {
				for(int j = 0; j < size; j++) {
					board[i][j] = (dice4.remove(boardGenerator.nextInt(dice4.size()))).charAt(boardGenerator.nextInt(6));
				}
			}
		}
		else {
			// dice face values for 5x5 board
			dice5.add("TESCCN");
			dice5.add("NODDTH");
			dice5.add("WROGVR");
			dice5.add("SIRFAA");
			dice5.add("WONDHH");
			dice5.add("EAEMEE");
			dice5.add("USENSS");
			dice5.add("YRYSIP");
			dice5.add("PLETSI");
			dice5.add("IPCEST");
			dice5.add("RHHLDO");
			dice5.add("ENNMGA");
			dice5.add("XBKZJB");
			dice5.add("RISAYF");
			dice5.add("LONDHR");
			dice5.add("RASAFA");
			dice5.add("WUNTOO");
			dice5.add("TTTOET");
			dice5.add("TELISI");
			dice5.add("UEEGAM");
			dice5.add("EAEAEE");
			dice5.add("TITIEI");
			dice5.add("NENNAD");
			dice5.add("OOOTTU");
			dice5.add("IPCEST");
			
			for(int i = 0; i < size; i++) {
				for(int j = 0; j < size; j++) {
					board[i][j] = (dice5.remove(boardGenerator.nextInt(dice5.size()))).charAt(boardGenerator.nextInt(6));
				}
			}
		}
		
		// put letters on the board the old way (w/ equiprobable letters)
		/*
		for(int x = 0; x < size; x++) {
			for(int y = 0; y < size; y++) {
				board[x][y] = Character.toUpperCase((char) (boardGenerator.nextInt(26) + 97));;
			}
		}
		
		// debugging
		for(int i = 0; i < size; i++) {
			for(int j = 0; j < size; j++) {
				System.out.print(board[i][j] + " ");
			}
			System.out.println();
		}
		*/
		
		for(String pName : players) {
			gameBoards.add(new Board(board, size, this, pName));
		}
	}
	
	protected void collectWordLists(String userName) {
		System.out.println(userName + " is collecting word lists from " + players.size() + " players...");
		for(Board b : gameBoards) {
			wordLists.add(b.getFndWords());
		}
	}
	
    /**
     * @return String a string representation of this game
     */
	public String toString() {
		if(players.size() < minPlayers) {
			return getOwner() + " is hosting a game for " + getMinPlayers() + " - " + getMaxPlayers() + " players with " + getMinPoints() + " - " + getMaxPoints() + " points on a " + getSize() + "x" + getSize() + " board with the " + getDictionary().getName() + " dictionary.";
		} else {
			return getOwner() + " hosted a game with " + getPlayers().size() + " players with " + getMinPoints() + " - " + getMaxPoints() + " points on a " + getSize() + "x" + getSize() + " board with the " + getDictionary().getName() + " dictionary.";
		}
	}
	
}
